Update_mode = {
    Immediately = 1,
    NextTick = 2,
    OnFree = 3,
    OnGet = 4
}

local observers = {}

local obf_target_key = {}
local target_key_obf = {}
local _nextTickFunc = {}
local _freeFuncs = {}
local _dirtys = {}
local _bindings = {}
local observerStack = {}

local _modes = {}

local function _getMode(func)
    return _modes[func]
end

local function addObTarget(fn, t, k)
    if not obf_target_key[fn] then
        obf_target_key[fn] = {}
    end
    if not obf_target_key[fn][t] then
        obf_target_key[fn][t] = {}
    end
    obf_target_key[fn][t][k] = true

    if not target_key_obf[t] then
        target_key_obf[t] = {}
    end
    if not target_key_obf[t][k] then
        target_key_obf[t][k] = {}
    end
    target_key_obf[t][k][fn] = true
end

local function removeObservers(fn)
    local targets = obf_target_key[fn]
    obf_target_key[fn] = nil
    if targets then
        for target, keys in pairs(targets) do
            local tbl = target_key_obf[target]
            if tbl then
                for key, _ in pairs(keys) do
                    if tbl[key] then tbl[key][fn] = nil end
                end                
            end
        end
    end
end


local testMark = {}
function addTestMark(fn)
    testMark[fn] = true
end
local function _runAndCollectTarget(fn)
    removeObservers(fn)
    table.insert(observerStack, fn)
    fn()
    table.remove(observerStack, #observerStack)
    _dirtys[fn] = nil
end

local _fn2objKey = {}
-- 针对bind做递归设置脏标记
-- 应对下一帧执行 a 监听b b监听c c发生变化后，要隔2帧才会应用到a 这种情况
-- 对任意一个bind的属性，当我get它时，我希望拿到的是最新的。
local function setDirty(func)
    _dirtys[func] = true
    local objkey = _fn2objKey[func]
    if objkey then
        local t = objkey[1]
        local k = objkey[2]
        local k_obf = target_key_obf[t]
        if k_obf then
            local obfs = k_obf[k]
            if obfs then
                for fn, _ in pairs(obfs) do
                    if _getMode(fn) ~= Update_mode.Immediately then
                        setDirty(fn)
                    -- else
                    --     _runAndCollectTarget(fn)
                    end
                end                
            end
        end
    end
end

-- 监察对象的元表
local metaObservable = {
    __index = function(t, k)
        if #observerStack > 0 then
            addObTarget(observerStack[#observerStack], t, k)
        end
        local bindings = _bindings[t]
        if bindings then
            local fn =  bindings[k]
            if fn and _dirtys[fn] then
                _runAndCollectTarget(fn)
            end
        end
        return t.__props[k]
    end,
    __newindex = function(t, k, v)
        t.__props[k] = v
        local k_obf = target_key_obf[t]
        if k_obf then
            local obfs = k_obf[k]
            if obfs then
                for fn, _ in pairs(obfs) do
                    if _getMode(fn) ~= Update_mode.Immediately then
                        setDirty(fn)
                    else
                        _runAndCollectTarget(fn)
                    end
                end                
            end
        end
    end
}

local function isObservable(target)
    return observers[target]
end

function Observable(target)
    if not isObservable(target) then
        return ToObservable(target)
    end
end

function ToObservable(target)
    local proxy = {}
    proxy.__props = target
    setmetatable(proxy, metaObservable)
    observers[proxy] = true
    return proxy
end

function ObservableList()
    return Observable{
        length = 0,
        maxValue = function(self, key)
            local val = 0
            for i = 1, self.length do
                val = val + self[i][key]
            end
            return val
        end
    }
end

function AutoRun(func, mode)
    mode = mode or Update_mode.Immediately
    _modes[func] = mode
    if mode == Update_mode.Immediately then
        _runAndCollectTarget(func)
    elseif mode == Update_mode.NextTick then
        _dirtys[func] = true
        table.insert(_nextTickFunc, func)
    elseif mode == Update_mode.OnFree then
        _dirtys[func] = true
        table.insert(_freeFuncs, func)
    end
end

-- 记录上一个bind相应后的值，避免反复设置
local _lastValues = {}
function Bind(obj, key, calculator, mode)
    UnBind(obj, key)
    _lastValues[obj] = {[key] = obj[key]}
    local func = function()
        -- notForce 默认为flase 也就是强制覆盖 当不希望相同属性再次覆盖时，可以通过返回true来控制
        local value, notForce = calculator()
        if not notForce and not (_lastValues[obj] and _lastValues[obj][key] == value) then
            obj[key] = value
            _lastValues[obj][key] = value
        end
    end

    AutoRun(func, mode)

    local map = _bindings[obj]
    if not map then
        map = {}
        _bindings[obj] = map
    end
    map[key] = func
    _fn2objKey[func] = {obj, key}
end

function UpdateObservers()
    for _, fn in pairs(_nextTickFunc) do
        if _dirtys[fn] then
            _runAndCollectTarget(fn)
        end
    end

    for _, fn in pairs(_freeFuncs) do
        if _dirtys[fn] then
            _runAndCollectTarget(fn)
            break
        end
    end
end

function StopRun(func)
    local mode = _modes[func]
    _modes[func] = nil
    if not mode then
        return
    end
    if mode == Update_mode.NextTick then
        for i = #_nextTickFunc, 1, -1 do
            if _nextTickFunc[i] == func then
                table.remove(_nextTickFunc, i)
                break
            end
        end
        _dirtys[func] = nil
    elseif mode == Update_mode.OnFree then
        for i = #_freeFuncs, 1, -1 do
            if _freeFuncs[i] == func then
                table.remove(_freeFuncs, 1)
                break
            end
        end
        _dirtys[func] = nil
    end
    removeObservers(func)
end

function UnBind(obj, key)
    local map = _bindings[obj]
    if not map then
        return
    end
    local func = map[key]
    if not func then
        return
    end
    StopRun(func)
    _bindings[obj][key] = nil
    _fn2objKey[func] = nil
end